//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#include <buola/buola.h>

#if BUOLA_PLATFORM_LINUX

#include <buola/io/cepollfdpoller.h>
#include <buola/io/cfdlistener.h>
#include <buola/algorithm/container.h>

#include <cerrno>
#include <sys/epoll.h>

namespace buola { namespace io {

CEPollFDPoller::CEPollFDPoller()
{
    mEpollFD=epoll_create1(0);
}

CEPollFDPoller::CEPollFDPoller(CEventLoop &pLoop)
    :   CFDPoller(pLoop)
{
    mEpollFD=epoll_create1(0);
}

CEPollFDPoller::~CEPollFDPoller()
{
    lock_guard<mutex> lLock(mFDMutex);
    for(int i=0;i<mFDs.size();++i)
        delete mFDs[i];
    for(int i=0;i<mDeletedFDs.size();++i)
        delete mDeletedFDs[i];
}

void CEPollFDPoller::AddFDListener(CFDListener *pListener,int pFD,ENotify pNotify,void *pClosure,ENotifyOp pMode)
{
    lock_guard<mutex> lLock(mFDMutex);

    std::map<int,SFD*>::iterator lFind=mFDs.find(pFD);

    if(lFind!=mFDs.end())
    {
        SFD *lFD=lFind->second;

        //we found it
        if(pMode==ENotifyOp::SET)
            lFD->mType=pNotify;
        else if(pMode==ENotifyOp::OR)
            lFD->mType|=pNotify;
        else if(pMode==ENotifyOp::AND)
            lFD->mType&=pNotify;
        else if(pMode==ENotifyOp::CLEAR)
            lFD->mType&=(~pNotify);

        if(lFD->mType==ENotify::NONE) //no type left, remove it
        {
            epoll_ctl(mEpollFD,EPOLL_CTL_DEL,lFD->mFD,nullptr);
            lFD->mDeleted=true;
            mDeletedFDs.push_back(lFD);
            mFDs.erase(lFind);
        }
        else //just modify it
        {
            epoll_event lEvent;
            lEvent.data.ptr=(void*)lFD;
            lEvent.events=0;
            if(lFD->mType&ENotify::READ)
                lEvent.events|=EPOLLIN;
            if(lFD->mType&ENotify::WRITE)
                lEvent.events|=EPOLLOUT;
            epoll_ctl(mEpollFD,EPOLL_CTL_MOD,lFD->mFD,&lEvent);
        }
    }
    else
    {
        //we didn't find it... just add it
        if(pMode==ENotifyOp::AND||pMode==ENotifyOp::CLEAR) return;
        if(pNotify==ENotify::NONE) return;
        
        SFD *lFD=new SFD;

        lFD->mListener=pListener;
        lFD->mType=pNotify;
        lFD->mFD=pFD;
        lFD->mClosure=pClosure;
        lFD->mDeleted=false;

        mFDs[pFD]=lFD;

        epoll_event lEvent;
        lEvent.data.ptr=(void*)lFD;
        lEvent.events=0;
        if(lFD->mType&ENotify::READ)
            lEvent.events|=EPOLLIN;
        if(lFD->mType&ENotify::WRITE)
            lEvent.events|=EPOLLOUT;
        epoll_ctl(mEpollFD,EPOLL_CTL_ADD,lFD->mFD,&lEvent);
    }
}

void CEPollFDPoller::RemoveFDListeners(int pFD)
{
    lock_guard<mutex> lLock(mFDMutex);

    std::map<int,SFD*>::iterator lFind=mFDs.find(pFD);
    
    if(lFind!=mFDs.end())
    {
        SFD *lFD=lFind->second;

        epoll_ctl(mEpollFD,EPOLL_CTL_DEL,lFD->mFD,nullptr);
        lFD->mDeleted=true;
        mDeletedFDs.push_back(lFD);
        mFDs.erase(lFind);
    }
}

void CEPollFDPoller::RemoveFDListeners(CFDListener *pListener)
{
    lock_guard<mutex> lLock(mFDMutex);

    for(std::map<int,SFD*>::iterator i=mFDs.begin();i!=mFDs.end();++i)
    {
        SFD *lFD=i->second;
        
        if(lFD->mListener==pListener)
        {
            epoll_ctl(mEpollFD,EPOLL_CTL_DEL,lFD->mFD,nullptr);
            lFD->mDeleted=true;
            mDeletedFDs.push_back(lFD);
            mFDs.erase(i);
            return;
        }
    }
}

bool CEPollFDPoller::WaitFDs(const chrono::nanoseconds &pDuration)
{
    epoll_event lEvents[32];

    if(mEventLoop)
        mEventLoop->GetMutex().unlock();

    int lNumFD=epoll_wait(mEpollFD,lEvents,32,chrono::duration_cast<chrono::milliseconds>(pDuration).count());

    if(mEventLoop)
        mEventLoop->GetMutex().lock();

    if(lNumFD<0)
    {
        if(errno==EINTR) return false;

        throw XInternal("error during epoll_wait"+c_error_string(errno));
    }

    bool lDispatched=false;

    unique_lock<mutex> lLock(mFDMutex);
    for(int i=0;i<lNumFD;i++)
    {
        SFD *lFD=(SFD*)lEvents[i].data.ptr;

        if(lFD->mDeleted) continue;

        ENotify lType=ENotify::NONE;

        if(lEvents[i].events&EPOLLIN)
        {
            lType|=ENotify::READ;
        }
        if(lEvents[i].events&EPOLLOUT)
        {
            lType|=ENotify::WRITE;
        }
        if(lEvents[i].events&(EPOLLHUP|EPOLLERR))
        {
            lType|=ENotify::EXCEPTION;
        }

        lType=lType&lFD->mType;
        
        if(lType!=ENotify::NONE)
        {
            lDispatched=true;
            lLock.unlock();
            ///\todo there is a race condition if it is removed now!!! check what to do
            lFD->mListener->OnFDListener(lFD->mFD,lType,lFD->mClosure);
            lLock.lock();
        }
    }

    for(int i=0;i<mDeletedFDs.size();++i)
    {
        delete mDeletedFDs[i];
    }
    mDeletedFDs.clear();

    return lDispatched;
}

/*namespace io*/ } /*namespace buola*/ }

#endif //BUOLA_PLATFORM_LINUX
